package com.yic.module.mes.service.report;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.yic.framework.security.core.util.SecurityFrameworkUtils;
import com.yic.module.base.api.center.CenterApi;
import com.yic.module.base.api.centeruser.CenterUserApi;
import com.yic.module.mes.controller.admin.reportworkhours.vo.ReportWorkHoursCreateReqVO;
import com.yic.module.mes.dal.dataobject.mo.MoDO;
import com.yic.module.mes.dal.dataobject.moroute.MoRouteDO;
import com.yic.module.mes.dal.dataobject.process.ProcessDO;
import com.yic.module.mes.dal.dataobject.report.ReportDO;
import com.yic.module.mes.service.itemproduce.ItemProduceService;
import com.yic.module.mes.service.mo.MoService;
import com.yic.module.mes.service.moroute.MoRouteService;
import com.yic.module.mes.service.process.ProcessService;
import com.yic.module.mes.service.reportworkhours.ReportWorkHoursService;
import com.yic.module.qms.api.inspectinfo.dto.InspectInfoCreateReqDTO;
import com.yic.module.qms.api.inspectprocess.InspectProcessApi;
import com.yic.module.qms.api.inspectprocess.dto.InspectProcessCreateReqDTO;
import com.yic.module.qms.api.inspectprocess.dto.InspectProcessRespDTO;
import com.yic.module.qms.api.inspectscheme.InspectSchemeApi;
import com.yic.module.qms.api.inspectscheme.dto.InspectSchemeRespDTO;
import com.yic.module.system.api.code.CodeApi;
import com.yic.module.system.api.user.AdminUserApi;
import com.yic.module.system.api.user.dto.AdminUserRespDTO;
import com.yic.module.system.enums.dict.*;
import org.springframework.security.core.parameters.P;
import org.springframework.transaction.support.TransactionTemplate;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

import static com.yic.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.yic.module.mes.enums.ErrorCodeConstants.*;
import static com.yic.module.system.enums.ErrorCodeConstants.USER_NOT_EXISTS;

/**
 * 报工抽象模板
 */
public abstract class ReportTemplete {

    @Resource
    private AdminUserApi adminUserApi;
    @Resource
    private CenterUserApi centerUserApi;
    @Resource
    private ReportService reportService;
    @Resource
    private MoRouteService moRouteService;
    @Resource
    private MoService moService;
    @Resource
    private ProcessService processService;
    @Resource
    private ReportWorkHoursService reportWorkHoursService;
    @Resource
    private CodeApi codeApi;
    @Resource
    private InspectSchemeApi inspectSchemeApi;
    @Resource
    private InspectProcessApi inspectProcessApi;
    @Resource
    private TransactionTemplate transactionTemplate;

    public final void report(Long moId, Long moRouteId, BigDecimal reportAmount, String serialNo, String cooperate, List<InspectInfoCreateReqDTO> entrylist){
        MoDO mo = getMo(moId);
        // 校验报工类型
        checkReportType(mo);
        // 校验工单工艺路线
        MoRouteDO route = checkRoutes(moRouteId);
        // 校验最后一道工序
        Integer lastFlag = checkLastRoute(route);
        // 校验流水号
        checkSerialNo(moId, moRouteId, serialNo);
        // 获取工艺路线详细信息
        ProcessDO process = getProcess(route.getProcessId());
        // 获取登录用户及用户所属部门
        AdminUserRespDTO user = getUser(SecurityFrameworkUtils.getLoginUserId());
        //校验工作中心（员工所属工作中心是否与生产工单所属工作中心一致）
        Set<Long> userIds = new HashSet<>();
        userIds.add(user.getId());
        if (StrUtil.isNotBlank(cooperate)) {
            Set<Long> cooperateList = StrUtil.split(cooperate, ",").stream().map(str -> Long.parseLong(str)).collect(Collectors.toSet());
            userIds.addAll(cooperateList);
        }
        checkCenter(process.getCenterId(), userIds, process.getCenterLimit());
        // 校验生产工单
        checkMo(mo);
        // 校验上一道工序
        checkProcess(moId, moRouteId, reportAmount, mo.getProduceAmount(), serialNo);

        transactionTemplate.execute(status -> {
            // 变更生产工单
            updateMo(moId, moRouteId, reportAmount);
            // 登记报工
            Long reportId = saveReport(mo, moRouteId, reportAmount, serialNo, cooperate, user.getId(), route.getProcessId(), process.getCenterId(), user.getDeptId(), lastFlag);
            // 登记工时
            saveWorkHours(reportId, user.getId(), cooperate, route.getWorkHours(), reportAmount);
            // 登记检验数据
            handleQms(mo, entrylist, process, reportAmount, user.getId());
            //TODO 报工扣料(确认方案)
            reduceInventory();
            //TODO 报工入库(确认方案)
            increaseInventory();
            //TODO 登记生产信息（一物一码）(确认方案)
            saveProduce();
            return Boolean.TRUE;
        });

    }

    /**
     * 获取工单
     * @param moId
     * @return
     */
    private MoDO getMo(Long moId) {
        // 获取生产工单
        MoDO mo = moService.getMo(moId);
        if (ObjectUtil.isEmpty(mo)) {
            throw exception(MO_NOT_EXISTS);
        }
        return mo;
    }

    /**
     * 获取工艺详细信息
     * @param processId
     * @return
     */
    private ProcessDO getProcess(Long processId) {
        ProcessDO process = processService.getProcess(processId);
        if (ObjectUtil.isEmpty(process)) {
            throw exception(PROCESS_NOT_EXISTS);
        }
        return process;
    }

    /**
     * 获取用户信息
     * @param userId
     * @return
     */
    private AdminUserRespDTO getUser(Long userId) {
        AdminUserRespDTO user = adminUserApi.getUser(userId);
        if (ObjectUtil.isEmpty(user)) {
            throw exception(USER_NOT_EXISTS);
        }
        return user;
    }

    /**
     * 校验工艺路线
     * @param moRouteId
     * @return
     */
    private MoRouteDO checkRoutes(Long moRouteId) {
        MoRouteDO moRoute = moRouteService.getMoRoute(moRouteId);
        if (ObjectUtil.isEmpty(moRoute)) {
            throw exception(MO_ROUTE_NOT_EXISTS);
        }
        return moRoute;
    }

    /**
     * 校验最后一道工序
     * @param route
     * @return
     */
    private Integer checkLastRoute(MoRouteDO route) {
        Integer lastFlag = SystemBooleanEnum.NO.getSystemBoolean();
        if (ObjectUtil.isEmpty(route.getNextProcessId())) {
            lastFlag = SystemBooleanEnum.YES.getSystemBoolean();
        }
        return lastFlag;
    }


    /**
     * 校验工作中心，依据工序中中心限定参数，决定是否验证
     * @param centerId
     * @param userIds
     * @param limit
     */
    private void checkCenter(Long centerId, Collection<Long> userIds, Integer limit) {
        if (MesCenterLimitEnum.YES.getMesCenterLimit().equals(limit) && !centerUserApi.checkCenterUserExists(centerId, userIds)) { //受工序中是否中心限制约束
            throw exception(REPORT_CENTER_USER_NOT_EXISTS);
        }
    }

    /**
     * 校验生产工单
     * 1.生产状态不能为已完成
     * @param mo
     */
    private void checkMo(MoDO mo) {
        if (mo.getStatus().equals(MesProduceStatusEnum.ED.getMesProduceStatus())) {
            throw exception(REPORT_MO_ED);
        }
    }

    private Long saveReport(MoDO mo, Long moRouteId, BigDecimal reportAmount, String serialNo, String cooperate, Long userId, Long processId, Long centerId, Long deptId, Integer lastProcess) {
        ReportDO reportDO = new ReportDO();
        reportDO.setMoId(mo.getId());
        reportDO.setMoCode(mo.getCode());
        reportDO.setPlanId(mo.getPlanId());
        reportDO.setPlanCode(""); //TODO 待实现
        reportDO.setBatchNo(mo.getBatchNo());
        reportDO.setItemId(mo.getItemId());
        reportDO.setItemProduceId(mo.getItemProduceId());
        reportDO.setFactoryId(mo.getFactoryId());
        reportDO.setWorkshopId(mo.getWorkshopId());
        reportDO.setCenterId(centerId);
        reportDO.setDeptId(deptId);
        reportDO.setUserId(userId);
        reportDO.setCooperate(cooperate);
        reportDO.setMoRouteId(moRouteId);
        reportDO.setProcessId(processId);
        reportDO.setLastProcess(lastProcess);
        reportDO.setReportAmount(reportAmount);
        reportDO.setSerialNo(serialNo);
        return reportService.saveReport(reportDO);
    }

    /**
     * 保存工时
     * @param reportId
     * @param userId
     * @param cooperate
     * @param workHours
     * @param reportAmount
     */
    private void saveWorkHours(Long reportId, Long userId, String cooperate, BigDecimal workHours, BigDecimal reportAmount) {
        List<ReportWorkHoursCreateReqVO> createReqVOS = new ArrayList<>();
        if (StrUtil.isBlank(cooperate)) { //无协作人，工时独享
            ReportWorkHoursCreateReqVO reqVO = new ReportWorkHoursCreateReqVO();
            reqVO.setReportId(reportId);
            reqVO.setUserId(userId);
            reqVO.setWorkHours(workHours);
            reqVO.setTotal(workHours.multiply(reportAmount));
            createReqVOS.add(reqVO);
        }else {// 存在协作人，工时平均
            List<Long> cooperateList = StrUtil.split(cooperate, ",").stream().map(str -> Long.parseLong(str)).collect(Collectors.toList());
            cooperateList.add(userId);
            BigDecimal newWorkHours = workHours.divide(new BigDecimal(String.valueOf(cooperateList.size())), 2, RoundingMode.DOWN);
            cooperateList.forEach(user -> {
                ReportWorkHoursCreateReqVO reqVO = new ReportWorkHoursCreateReqVO();
                reqVO.setReportId(reportId);
                reqVO.setUserId(user);
                reqVO.setWorkHours(newWorkHours);
                reqVO.setTotal(newWorkHours.multiply(reportAmount));
                createReqVOS.add(reqVO);
            });
        }
        reportWorkHoursService.batchCreateReportWorkHours(createReqVOS);
    }

    /**
     * 更新
     * @param id
     * @param moRouteId
     * @param reportAmount
     */
    private void updateMo(Long id, Long moRouteId, BigDecimal reportAmount) {
        moService.changeAmount(id, moRouteId, reportAmount, null, null, null);
    }

    private void handleQms(MoDO mo, List<InspectInfoCreateReqDTO> entrylist, ProcessDO process, BigDecimal reportAmount, Long userId) {
        if (QualityTypeEnum.NEED.getQualityType().equals(process.getQualityType()) && CollectionUtil.isNotEmpty(entrylist)) { //执行检验
            // 查询检验方案
            InspectSchemeRespDTO inspectScheme = inspectSchemeApi.getInspectScheme(entrylist.get(0).getSchemeId());
            if (inspectScheme.getSampleType().equals(QmsSampleTypeEnum.FIRST.getQmsSampleType())) {
                // 检验类型为首检时，需要判断是否已经进行过首检，如果已经首检，直接返回
                List<InspectProcessRespDTO> inspectProcessList = inspectProcessApi.getInspectProcessList(mo.getId(), process.getId());
                if (!CollectionUtil.isEmpty(inspectProcessList)) {
                    return;
                }
            }
            // 组装数据，保存质检数据
            InspectProcessCreateReqDTO req = new InspectProcessCreateReqDTO();
            req.setCode(codeApi.getCode(null));// TODO 编码规则待实现
            req.setFactoryId(mo.getFactoryId());
            req.setSchemeId(inspectScheme.getId());
            req.setPlanId(mo.getPlanId());
            req.setMoId(mo.getId());
            req.setItemId(mo.getItemId());
            req.setItemProduceId(mo.getItemProduceId());
            req.setProcessId(process.getId());
            req.setInspectAmount(reportAmount);
            req.setQualifiedAmount(reportAmount);
            req.setUnqualifiedAmount(BigDecimal.ZERO);
            req.setInspectResult(InspectResultEnum.PASS.getInspectResult());
            req.setUserId(userId);
            req.setInspectTime(LocalDateTime.now());
            req.setRemark("MES报工抛转");
            req.setInspectType(inspectScheme.getInspectType());
            req.setEntryList(entrylist);
            inspectProcessApi.saveInspectProcess(req);
        }
    }

    /**
     * 校验报工类型
     * @param moDO
     */
    protected abstract void checkReportType(MoDO moDO);


    protected abstract void checkSerialNo(Long moId, Long moRouteId, String serialNo);

    /**
     * 校验工序
     * @param moId
     * @param moRouteId
     * @param reportAmount
     * @param produceAmount
     * @param serialNo
     */
    protected abstract void checkProcess(Long moId, Long moRouteId, BigDecimal reportAmount, BigDecimal produceAmount, String serialNo);

    /**
     * 出库
     */
    protected abstract void  reduceInventory();

    /**
     * 入库
     */
    protected abstract void  increaseInventory();

    /**
     * 生成生产信息
     */
    protected abstract void saveProduce();






}
